/*
\file
\author Zachary Wartell

\brief Hello World-OpenGL is a simple OpenGL program demonstrating OpenGL raster and framebuffer operations.
It uses GLUT, GLEW, and the FreeImage library.


REFERENCES:

- 
*/
#include <GL/glew.h>
#include <GL/freeglut.h>

#include <BlittableImage.h>
#include <Misc.h>
#include <OpenGLTrainer/Timer.h>

#include <FreeImage.h>

#include <cassert>
#include <cmath>
#include <math.h>
#include <iostream>

using namespace std;
using namespace ITCS4120;

/**
\brief Rect is a simplistic rectangle class

\todo If this class is ever used outside this file, create a separate .h/.cpp for it.
*/
struct Rect
    {
    float x0;
    float y0;
    float x1;
    float y1;

    void poseRandomly();    
    void lowerLeft(float x0_, float y0_);
    void animate (OpenGLTrainer::Timer::Seconds dt);

    /* angle of spin rotation */
    float angle;

    /* x,y location of center of spin rotation */
    float spinX;
    float spinY;

    static const float RADIUS;
    static const float ANGULAR_VELOCITY;
    };

const float Rect::RADIUS = 50.0f;
const float Rect::ANGULAR_VELOCITY = 2.0f*(atan(1.0f)*4.0f)*0.1;//*PI_f;//*0.1f; // radians per second

/* 
\brief Move this Rect to new random pose (location,size) bounded by size of current GLUT window

\warning A current GLUT window must be active, otherwise results are undefined
*/
void Rect::poseRandomly()
    {
    const int
	windowWidth = glutGet(GLUT_WINDOW_WIDTH),
	windowHeight = glutGet(GLUT_WINDOW_HEIGHT);

    angle = 0.0;

    x0 = float(rand() % windowWidth);
    y0 = float(rand() % windowHeight);

    x1 = rand() % (windowWidth - (int)x0 ) + x0;
    y1 = rand() % (windowWidth - (int)y0 ) + y0;

    spinX = (x1-x0)/2.0f;
    spinY = (y1-y0)/2.0f;
    }

/**
\brief advance this Rect's position assuming 'dt' seconds has past.

Specifically, spin the rect around the spin center location.
*/
void Rect::animate(OpenGLTrainer::Timer::Seconds dt)
    {
    angle += ANGULAR_VELOCITY * (float)dt;
    angle = fmod(angle,2.0f*PI_f);
    std::cout << ANGULAR_VELOCITY << ' ' << angle << endl;
    float x,y;
    x = spinX + RADIUS*cos(angle);
    y = spinY + RADIUS*sin(angle);

    x1 += x - x0;
    y1 += y - y0;
    x0 = x;
    y0 = y;    
    }

/*
\brief a animated rectangle
*/
Rect rect;

/** 
\brief blittableImage is loaded with a image file and blit'ed to the the screen
*/
BlittableImage* blittableImage=nullptr;

/**
\brief firstDisplayCall records whether the display callback has been called at least once
*/
static bool firstDisplayCall=true;

/** 
\brief Perform one-time initializations that must be done after:
    - the OpenGL context is established
    - the GLUT window is first displayed.
*/
void initOnFirstDisplay (void)  
{    
    firstDisplayCall=false; 

    ::blittableImage = new BlittableImage();

    glClearColor(1.0,1.0,1.0,0.0);
    
    ::rect.x0 = 0;
    ::rect.y0 = 0;
    ::rect.x1 = float(glutGet(GLUT_WINDOW_WIDTH));
    ::rect.y1 = float(glutGet(GLUT_WINDOW_HEIGHT));

    return;
}

/** 
\author Zachary Wartell
\brief pause program and prompt user to hit return key.
*/
inline void pause_prompt() { cout << "hit the return key to continue: "; int c=getchar(); }

/** 
\author Zachary Wartell
\brief Verify the required version of OpenGL is available
*/
static 
bool OpenGLVersionCheck
	(
	GLenum err /* [IN]: error value from glewInit */
	)
    {
    if (GLEW_OK != err)
	{/* glewInit failed, abort with message */
	cerr << "Fatal Error: " << glewGetErrorString(err) << endl;
	pause_prompt();
	return false;
	}
    cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << endl;
    cout << "Status: Using GL " << glGetString(GL_VERSION) << endl;  

    /*
    Require support for OpenGL 3.3 or greater
    */
    const char 
	major [] = {glGetString(GL_VERSION)[0],'\0'},
	minor [] = {glGetString(GL_VERSION)[2],'\0'};
    if ( (atoi(major) <  3) ||
	((atoi(major) == 3) && atoi(minor) < 3))
	{
	cerr << 
	    "Fatal Error: Unable to get OpenGL 3.3 context or better." << endl << 
	    "   This computer's graphics card and OpenGL driver are too old to run this program." << endl;
	pause_prompt();
	return false;
	}
    return true;
    }
    
/** 
\brief Display perform our OpenGL drawing
*/
void display(void)
{
    /* perform misc one-time initialization */    
    if (firstDisplayCall) 
	initOnFirstDisplay ();

    // error check before any rendering
    gltCheck();

    /* clear frame buffer */
    glClear (GL_COLOR_BUFFER_BIT);

    /*
	blit off-screen image to random location on back buffer 
    */
    // assign the blittableImage's off-screen framebuffer as the blit operation's read buffer
    glBindFramebuffer(GL_READ_FRAMEBUFFER,blittableImage->frameBuffer());    
    glReadBuffer(GL_COLOR_ATTACHMENT0);    

    // assign the OpenGL back buffer as the blit operation's write buffer
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
    glDrawBuffer(GL_BACK);
        
    // do blit
    glBlitFramebuffer(0,0,blittableImage->width(),blittableImage->height(),
		      GLint(::rect.x0),GLint(::rect.y0),GLint(::rect.x1),GLint(::rect.y1),
		      GL_COLOR_BUFFER_BIT,GL_NEAREST);		    // note, by default the draw buffer is the GL_BACK buffer


    // error check after rendering
    gltCheck();

    /* swap buffers */
    glutSwapBuffers();
    glutPostRedisplay();
}

/** 
\brief keyboard (a callback) handles keyboard input
*/
void keyboard( unsigned char key, int x, int y )
    {
    switch ( key ) 
	{
	case 033:
	    /* escape key, exit */
	    exit( EXIT_SUCCESS );
	    break;
	case ' ':
	    {
	    /* space bar, computer new random destination rectangle */
	    ::rect.poseRandomly();
	    break;
	    }
	}
    }

/** 
\brief idle (a callback) handles background computation

\warning The GLUT specification does not allow calling OpenGL functions in the idle callback!  Avoid doing so, otherwise
your code will not be portable.
*/
void idle()
    {
    static OpenGLTrainer::Timer frameDelta; // timer for computing time change (delta) since last call to idle function         

    /* mark time since last call */
    frameDelta.lap();
    cout << "idle call interval: " << frameDelta.lastLapInterval() << "(s)" << endl;

    /* animate ::rect */    
    ::rect.animate(frameDelta.lastLapInterval());
    }

int main(int argc, char** argv)
{
   /* 
   Init FreeImage library
   */
   FreeImage_Initialise();

   /*
   Init GLUT
   */
   glutInit(&argc, argv);

   /*
   Initialize OpenGL Version 3.3.
   
   Version 3.3 or better is required for use in ITCS 4120.  Ideally students will use exactly
   version 3.3 to prevent them from accidently stumbling into using OpenGL 4.0 features. However, some 
   OpenGL graphics drivers that support OpenGL 4.x have deprecated its ability to enforce the use of
   only 3.3 features.  In such cases students should disable this next block of code by relacing #if 1 with #if 0
   */
  #if 0
   // ZJW: Note, this seems to fail more often than not, circa 9/2013 so I leave it disabled...
   // enforce core 3.3 profile of OpenGL 
   glutInitContextVersion( 3, 3 );
   //glutInitContextFlags(GLUT_DEBUG);
   glutInitContextProfile( GLUT_CORE_PROFILE );  
   if (glGetError() != GL_NO_ERROR)
       {
       cerr << 
	   "Fatal Error: Unable to enforce OpenGL 3.3 context." << endl << 
	   "   Try recompiling and running with #if 0 as described in the source code." << endl;
       pause_prompt();
       return 1;
       }
  #else
   // enforce core profile of the default version of OpenGL (presumably > 3.3)
   glutInitContextProfile( GLUT_CORE_PROFILE );  
  #endif

   /* 
   Declare initial window size, position, and display mode (double buffer and RGBA).  
   */
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize (640, 480); 
   glutInitWindowPosition (100, 100);

   /*
   Create window
   */
   glutCreateWindow ("ITCS 4120: Hello World");

   /* 
   Init Glew
   */
   GLenum err = glewInit();  
   if (!OpenGLVersionCheck(err))
       return 1;
   
   /* 
   Set callbacks
   */
   glutDisplayFunc(display); 
   glutKeyboardFunc(keyboard); 
   glutIdleFunc(idle);

   /*
   enter main loop
   */
   glutMainLoop();

   return 0;   /* ANSI C requires main to return int. */
}